﻿using Nx.Logging;
using Nx.ViewModels;
using Ninject;
using System;
using System.Collections.Generic;

#if WINDOWS_PHONE
using System.Text;
using System.Windows;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
#elif NETFX_CORE
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
#endif

namespace Nx.UI.Navigation
{
    public class NavigationModel : INavigationModel
    {
#if NETFX_CORE
        private Dictionary<object, Type> Views = new Dictionary<object, Type>();
#elif WINDOWS_PHONE
        private Dictionary<object, Uri> Views = new Dictionary<object, Uri>();
#endif
        private Dictionary<object, Type> ViewModels;
        private IKernel Kernel;
        private ILogger Logger;

        public NavigationModel(IKernel kernel)
        {
            this.Kernel = kernel;
            this.Logger = this.Kernel.Get<ILogFactory>().CreateLogger("NAV");
            this.ViewModels = new Dictionary<object, Type>();
        }

        #region IDisposable Members
        ~NavigationModel()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                Clear();

                if (_NavigationFrame != null)
                {
                    _NavigationFrame.Navigated -= OnNavigating;
                    _NavigationFrame = null;
                }

                Kernel = null;
            }
        }
        #endregion

        public void Clear()
        {
            if (Views != null)
            {
                this.Views.Clear();
            }

            if (ViewModels != null)
            {
                this.ViewModels.Clear();
            }
        }

        #region NavigationFrame
#if WINDOWS_PHONE
        private PhoneApplicationFrame _NavigationFrame;
        public PhoneApplicationFrame NavigationFrame
        {
            get
            {
                if (_NavigationFrame == null)
                {
                    _NavigationFrame = Application.Current.RootVisual as PhoneApplicationFrame;
                    _NavigationFrame.Navigated += OnNavigating;
                }

                return _NavigationFrame;
            }
        }
#elif SILVERLIGHT && !WINDOWS_PHONE
        private Frame _NavigationFrame;
        public Frame NavigationFrame
        {
            get
            {
                if (_NavigationFrame == null)
                {
                    _NavigationFrame = (Application.Current.RootVisual).Descendants()
                        .SingleOrDefault(d => d is Frame) as Frame;

                    if (_NavigationFrame != null)
                    {
                        _NavigationFrame.Navigated += OnNavigating;
                    }
                }

                return _NavigationFrame;
            }
        }
#elif NETFX_CORE
        private Frame _NavigationFrame;
        public Frame NavigationFrame
        {
            get
            {
                return _NavigationFrame;
            }

            set
            {
                if (_NavigationFrame == null)
                {
                    _NavigationFrame = value;
                    _NavigationFrame.Navigated += OnNavigating;
                }
            }
        }
#endif
        #endregion

        #region Frame Navigation
        public void GoBack()
        {
            if (NavigationFrame != null && NavigationFrame.CanGoBack)
            {
                Logger.Info("Navigating back");
                NavigationFrame.GoBack();
            }
        }


        public void GoHome()
        {
            if (NavigationFrame != null)
            {
                Logger.Info("Navigating home");

                while (this.NavigationFrame.CanGoBack)
                {
                    this.NavigationFrame.GoBack();
                }
            }
        }

        public void GoForward()
        {
            if (NavigationFrame != null && this.NavigationFrame.CanGoForward)
            {
                Logger.Info("Navigating forward");

                this.NavigationFrame.GoForward();
            }
        }

        #endregion

        #region Page Navigation
        public void NavigateTo(object key)
        {
            this.NavigateTo(key, null);
        }

        public void NavigateTo(object key, INavigationArgument argument)
        {
            if (!this.Views.ContainsKey(key))
            {
                Logger.Error("An attempt to navigate to an unregistered page was detected");
                return;
            }

#if !NETFX_CORE
            Uri item = this.Views[key];
            Dictionary<string, string> arguments = argument != null ? argument.GetArgument<Dictionary<string, string>>() : null;
            if (arguments != null)
            {
                StringBuilder stringBuilder = new StringBuilder();
                foreach (KeyValuePair<string, string> arg in arguments)
                {
                    stringBuilder.Append(arg.Key);
                    stringBuilder.Append("=");
                    stringBuilder.Append(arg.Value);
                }
                string originalString = item.OriginalString;
                item = new Uri(string.Concat(originalString, "?", stringBuilder), UriKind.RelativeOrAbsolute);
            }
#endif
            if (NavigationFrame != null)
            {
#if!NETFX_CORE
                Logger.Info("Navigating to : {0}", item.OriginalString);
#else
                Logger.Info("Navigating to : {0}", key.ToString());
#endif

                try
                {
#if!NETFX_CORE
                    NavigationFrame.Navigate(item);
#else
                    NavigationFrame.Navigate(Views[key], argument != null ? argument.GetArgument<object>() : null);
#endif
                }
                catch (Exception e)
                {
                    Logger.Error("Error while attempting to navigate : {0}", e.Message);
                }
            }
            else
            {
                Logger.Error("Navigation Frame uninitialized");
            }
        }
        #endregion

        #region Registration
        public void Register(object key, INavigationTarget target)
        {
            RegisterView(key, target);
        }

        public void Register(object key, Type viewModelType, INavigationTarget target)
        {
            RegisterView(key, target);
            RegisterViewModel(key, viewModelType);
        }

        public void RegisterView(object key, INavigationTarget target)
        {
            if (this.Views.ContainsKey(key))
            {
                Logger.Error("{0} Page already registered", key);
                return;
            }

#if NETFX_CORE
            this.Views.Add(key, target.GetTarget<Type>());
#elif WINDOWS_PHONE
            this.Views.Add(key, target.GetTarget<Uri>());
#endif
        }

        public void RegisterViewModel(object key, Type viewModelType)
        {
            this.ViewModels.Add(key, viewModelType);
        }

        public void UnRegister(object key)
        {
            if (this.Views.ContainsKey(key))
            {
                this.Views.Remove(key);
            }

            if (this.ViewModels.ContainsKey(key))
            {
                this.ViewModels.Remove(key);
            }
        }
        #endregion

#if !NETFX_CORE
        private object GetViewKey(Uri value)
#else
        private object GetViewKey(Type value)
#endif
        {
            foreach (var kvp in this.Views)
            {
#if !NETFX_CORE
                if (kvp.Value.Equals(value))
#else
                if (kvp.Value.Equals(value))
#endif
                {
                    return kvp.Key;
                }
            }

            throw new InvalidOperationException("Key not found");
        }

        private void OnNavigating(object sender, NavigationEventArgs e)
        {
#if !NETFX_CORE
            if (this.Views.ContainsValue(e.Uri))
#else
            if (this.Views.ContainsValue(e.SourcePageType))
#endif
            {
                try
                {
#if !NETFX_CORE
                    object key = GetViewKey(e.Uri);
#else
                    object key = GetViewKey(e.SourcePageType);
#endif

                    if (this.ViewModels.ContainsKey(key))
                    {
#if !NETFX_CORE
                        Logger.Info("ViewModel for {0} found. Resolving", e.Uri.OriginalString);
#else
                        Logger.Info("ViewModel for {0} found. Resolving", e.SourcePageType.Name);
#endif
#if WINDOWS_PHONE
                        (e.Content as PhoneApplicationPage).DataContext = Kernel.Get(this.ViewModels[key]);
#elif NETFX_CORE || (SILVERLIGHT && !WINDOWS_PHONE)
                        (e.Content as Page).DataContext = Kernel.Get(this.ViewModels[key]);
#endif
                    }
                    else
                    {
                        Logger.Info("No ViewModel for {0} found.", e.Uri.OriginalString);
                    }
                }
                catch (InvalidOperationException ex)
                {
                    Logger.Error(ex.Message);
                }
            }
        }
    }

    public class NavigationModel<T> : INavigationModel<T>, IDisposable
    {
        /// <summary>
        /// The core navigation model shared between all implementations of the INavigationT.
        /// Now owned by the instance of INavigationT
        /// </summary>
        private INavigationModel _NavigationModel;

        public NavigationModel(INavigationModel navigationModel)
        {
            this._NavigationModel = navigationModel;
        }

        #region IDisposable Members
        ~NavigationModel()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                this._NavigationModel = null;
            }
        }
        #endregion

        public void NavigateTo(T type)
        {
            this.NavigateTo(type, null);
        }

        public void NavigateTo(T type, INavigationArgument argument)
        {
            this._NavigationModel.NavigateTo(type, argument);
        }

        #region Registration
        private void RegisterView(T type, INavigationTarget target)
        {
            this._NavigationModel.RegisterView(type, target);
        }

        public void Register(T type, INavigationTarget target)
        {
            this._NavigationModel.Register(type, target);
        }

#if NETFX_CORE
        public void Register<TViewModel, TView>(T type)
            where TViewModel : ViewModelBase
            where TView : Page
        {
            Register<TViewModel>(type, new NavigationTarget(typeof(TView)));
        }
#endif

        public void Register<TViewModel>(T type, INavigationTarget target)
        where TViewModel : ViewModelBase
        {
            Register(type, typeof(TViewModel), target);
        }

        public void Register(T type, Type viewModelType, INavigationTarget target)
        {
            this._NavigationModel.Register(type, viewModelType, target);
        }

        public void UnRegister(T type)
        {
            this._NavigationModel.UnRegister(type);
        }
        #endregion
    }
}
